/* //device/system/broncho-ril/reference-ril.c
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

#include <telephony/ril.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <alloca.h>
#include "atchannel.h"
#include "at_tok.h"
#include "ril_util.h"
#include "broncho-ril.h"
#include "ril-cc.h"

#define LOG_TAG "RIL"
#include "broncho_log.h"

typedef struct _CcContext
{
	RIL_LastCallFailCause 	failCase;

	int						exist_call;
	int						has_wait_call;
}CcContext;

static int getActiveCallIntext(RIL* thiz);

static CcContext* ccGetContext(RIL* thiz)
{
	CcContext* ctx = RIL_GetPrivInfo(thiz, "cc_ctx");
	if(ctx == NULL)
	{
		ctx = calloc(1, sizeof(CcContext));
		RIL_SetPrivInfo(thiz, ctx, "cc_ctx");
	}

	return ctx;
}

int RIL_ExistCalls(RIL* thiz)
{
	CcContext* ctx = RIL_GetPrivInfo(thiz, "cc_ctx");

	return  ctx->exist_call;
}

static RIL_LastCallFailCause ccStrToFailCase(const char* line)
{
	RIL_LastCallFailCause ret = CALL_FAIL_NORMAL;

	if(strStartsWith(line, "NO CARRIER"))
	{
		ret = CALL_FAIL_CONGESTION;
	}
	else if(strStartsWith(line, "NO ANSWER"))
	{
		ret = CALL_FAIL_BUSY;
	}

	return ret;
}

static void ccHandleCallResult(RIL* thiz, const char* line)
{
	CcContext* ctx = ccGetContext(thiz);

	ctx->failCase = ccStrToFailCase(line);

	return;
}

static int clccStateToRILState(int state, RIL_CallState *p_state)

{
    switch(state) {
        /*for think-will version 1.2*/
		case 1: *p_state = RIL_CALL_DIALING; return 0;
        case 3: *p_state = RIL_CALL_ACTIVE;   return 0;
        case 2: *p_state = RIL_CALL_INCOMING; return 0;
		default: return -1;
        /*for version 1.1*/
		/*
		case 0: *p_state = RIL_CALL_DIALING; return 0;
        case 1: *p_state = RIL_CALL_DIALING;  return 0;
        case 2: *p_state = RIL_CALL_INCOMING; return 0;
		case 3: *p_state = RIL_CALL_ACTIVE;   return 0;
		default: *p_state = RIL_CALL_DIALING; return 0;
		*/
    }
}

/**
 * Note: directly modified line and has *p_call point directly into
 * modified line
 */
static int callFromCLCCLine(char *line, RIL_Call *p_call)
{
        //+CLCC: 1,0,2,0,0,\"+18005551212\",145
        //     index,isMT,state,mode,isMpty(,number,TOA)?

    int err;
    int state;
    int mode;
	
	do
	{
	    if((err = at_tok_start(&line)) < 0)
		{
			ALOGE("callFromCLCCLine: start error\n");
			break;
		}
	    
		if((err = at_tok_nextint(&line, &(p_call->index))) < 0)
		{
			ALOGE("callFromCLCCLine: index error\n");
			break;
		}

	    if((err = at_tok_nextbool(&line, &(p_call->isMT))) < 0)
		{
			ALOGE("callFromCLCCLine: MT error\n");
			break;
		}

	    if((err = at_tok_nextint(&line, &state)) < 0)
		{
			ALOGE("callFromCLCCLine: state error\n");
			break;
		}

	    if((err = clccStateToRILState(state, &(p_call->state))) < 0)
		{
			ALOGE("callFromCLCCLine: clcc state error\n");
			break;
		}

	    if((err = at_tok_nextint(&line, &mode)) < 0)
		{
			ALOGE("callFromCLCCLine: mode error\n");
			break;
		}
	    p_call->isVoice = (mode == 0);
    	
		if((err = at_tok_nextbool(&line, &(p_call->isMpty))) < 0)
		{
			ALOGE("callFromCLCCLine: mpty error\n");
			break;
		}

    	if (at_tok_hasmore(&line)) {
        	if((err = at_tok_nextstr(&line, &(p_call->number))) < 0)
			{
				ALOGE("callFromCLCCLine: number error\n");
				break;
			}

        	// Some lame implementations return strings
	        // like "NOT AVAILABLE" in the CLCC line
    	    if (p_call->number != NULL
        	    && 0 == strspn(p_call->number, "+0123456789")
	        ) {
    	        p_call->number = NULL;
        	}

	        if((err = at_tok_nextint(&line, &p_call->toa)) < 0)
			{
				ALOGE("callFromCLCCLine: toa error\n");
				break;
			}
    	}
	}while(0);

    return err;
}

static void requestGetCurrentCalls(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
    int err = 0;
    ATResponse *p_response = NULL;
    ATLine *p_cur = NULL;
    int countCalls = 0;
    int countValidCalls = 0;
    RIL_Call *p_calls = NULL;
    RIL_Call **pp_calls = NULL;
    int i = 0;
    int needRepoll = 0;
	CcContext* ctx = ccGetContext(thiz);

    err = ril_send_command_multiline (thiz, "AT+CLCC", "+CLCC:", &p_response);
    if (err != 0 || p_response->success == 0) {
        RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
        return;
    }

    /* count the calls */
    for (countCalls = 0, p_cur = p_response->p_intermediates
            ; p_cur != NULL
            ; p_cur = p_cur->p_next
    ) {
        countCalls++;
    }

    /* yes, there's an array of pointers and then an array of structures */

    pp_calls = (RIL_Call **)alloca(countCalls * sizeof(RIL_Call *));
    p_calls = (RIL_Call *)alloca(countCalls * sizeof(RIL_Call));
    memset (p_calls, 0, countCalls * sizeof(RIL_Call));

    /* init the pointer array */
    for(i = 0; i < countCalls ; i++) {
        pp_calls[i] = &(p_calls[i]);
    }

    for (countValidCalls = 0, p_cur = p_response->p_intermediates
            ; p_cur != NULL
            ; p_cur = p_cur->p_next
    ) {
        err = callFromCLCCLine(p_cur->line, p_calls + countValidCalls);

        if (err != 0) {
            continue;
        }

        if (p_calls[countValidCalls].state != RIL_CALL_ACTIVE
            && p_calls[countValidCalls].state != RIL_CALL_HOLDING
        ) {
            needRepoll = 1;
        }

		if(p_calls[countValidCalls].state == RIL_CALL_ACTIVE)
		{
			if(ctx->exist_call == 0)
			{
				ril_command_exec_for_result(thiz, "AT+SIDETONE=0");
				ril_command_exec_for_result(thiz, "AT+ECHOC=3");
			}
			ctx->exist_call = 1;
		}

        countValidCalls++;
    }
	
	if(countValidCalls == 0)
	{
		ctx->has_wait_call = 0;
		ctx->exist_call = 0;
	}

    RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, pp_calls,
            countValidCalls * sizeof (RIL_Call *));
    at_response_free(p_response);

	return;
}

static void requestDial(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
    RIL_Dial *p_dial;
    char cmd[64];
    int ret;
	ATResponse* p_resp = NULL;
	int index = getActiveCallIntext(thiz);

    p_dial = (RIL_Dial *)data;

	if(index == -1)
	{
    	snprintf(&cmd, 63, "AT+CDV=%s;", p_dial->address);
	}
	else
	{
		snprintf(&cmd, 63, "AT^FLASH=%s", p_dial->address);
	}
	ret = ril_send_command(thiz, cmd, &p_resp);

	ccHandleCallResult(thiz, p_resp->finalResponse);

    /* success or failure is ignored by the upper layer here.
       it will call GET_CURRENT_CALLS and determine success that way */
    RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
}

static void requestDtmfStart(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	char c = ((char*)data)[0];
	char cmd[32] = {0};
	int index = getActiveCallIntext(thiz);
	
	snprintf(cmd, 32, "AT^DTMF=%d,%c,1", index, c);
	if(index != -1 && ril_command_exec_for_result(thiz, cmd))
	{
		RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
	}
	else
	{
		RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
	}

	return;
}

static void requestCdmaBurstDtmf(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	char* dtmfStr = ((char**)data)[0];
	char* onStr = ((char**)data)[1];
	char* offStr = ((char**)data)[2];
	int i = 0;
	int dtmfLen = 0;
	int index = getActiveCallIntext(thiz);
	int success = 1;

	if(index != -1)
	{
		int on = atoi(onStr);
		int off = atoi(offStr);
		dtmfLen = strlen(dtmfStr);
		for (i = 0; i < dtmfLen; i++)
		{
			char cmd[32] = {0};

			snprintf(cmd, 32, "AT^DTMF=%d,%c,%d,%d", index, dtmfStr[i], on, off);
			if(!ril_command_exec_for_result(thiz, cmd))
			{
				success = 0;
				break;
			}
		}
	}
	else
	{
		success = 0;
	}

	if(success)
	{
		RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
	}
	else
	{
		RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
	}

	return;
}

static void requestCdmaFlash(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	char* featureCode = (char*)data;
	char cmd[32] = {0};

	if(featureCode == NULL || featureCode[0] == '\0')
	{
		snprintf(cmd, 32, "AT^FLASH");
	}
	else
	{
		snprintf(cmd, 32, "AT^FLASH=%s", featureCode);
	}

	if(ril_command_exec_for_result(thiz, cmd))
	{
		RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
	}
	else
	{
		RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
	}

	return;
}

static void requestLastCallFailCause(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	CcContext* ctx = ccGetContext(thiz);
	int failCase = ctx->failCase;
	
	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, &failCase, sizeof(failCase));

	return;
}

static void requestDtmfStop(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	char cmd[32] = {0};
	int index = getActiveCallIntext(thiz);

	snprintf(cmd, 32, "AT^DTMF=%d,0,0", index);
	if(index != -1 && ril_command_exec_for_result(thiz, cmd))
	{
		RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
	}
	else
	{
		RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
	}

	return;
}

static int getActiveCallIntext(RIL* thiz)
{
	int err = 0;
    ATResponse *p_response = NULL;
    ATLine *p_cur = NULL;
    int countCalls = 0;
    int countValidCalls = 0;
    RIL_Call *p_calls = NULL;
    RIL_Call **pp_calls = NULL;
    int i = 0;
    int needRepoll = 0;
	int activeIndex = -1;

    err = ril_send_command_multiline (thiz, "AT+CLCC", "+CLCC:", &p_response);
    if (err != 0 || p_response->success == 0) {
        return -1;
    }

    /* count the calls */
    for (countCalls = 0, p_cur = p_response->p_intermediates
            ; p_cur != NULL
            ; p_cur = p_cur->p_next
    ) {
        countCalls++;
    }

    /* yes, there's an array of pointers and then an array of structures */

    pp_calls = (RIL_Call **)alloca(countCalls * sizeof(RIL_Call *));
    p_calls = (RIL_Call *)alloca(countCalls * sizeof(RIL_Call));
    memset (p_calls, 0, countCalls * sizeof(RIL_Call));

    /* init the pointer array */
    for(i = 0; i < countCalls ; i++) {
        pp_calls[i] = &(p_calls[i]);
    }

    for (countValidCalls = 0, p_cur = p_response->p_intermediates
            ; p_cur != NULL
            ; p_cur = p_cur->p_next
    ) {
        err = callFromCLCCLine(p_cur->line, p_calls + countValidCalls);

        if (err != 0) {
            continue;
        }

        if (p_calls[countValidCalls].state != RIL_CALL_ACTIVE
            && p_calls[countValidCalls].state != RIL_CALL_HOLDING
        ) {
            needRepoll = 1;
        }

        countValidCalls++;
    }
    at_response_free(p_response);

	for(i = 0; i < countValidCalls; i++)
	{
		if(p_calls[i].state == RIL_CALL_ACTIVE)
		{
			activeIndex = p_calls[i].index;
			ALOGD("CDMACC: activeIndex=%d\n", activeIndex);
			break;
		}
	}

	return activeIndex;
}

static void requestDtmf(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	char c = ((char*)data)[0];
	char cmd[32] = {0};
	int index = getActiveCallIntext(thiz);
	
	snprintf(cmd, 32, "AT^DTMF=%d,%c", index, c);
	if(index != -1 && ril_command_exec_for_result(thiz, cmd))
	{
		RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
	}
	else
	{
		RIL_onRequestComplete(thiz, t, RIL_E_GENERIC_FAILURE, NULL, 0);
	}

	return;
}

static void requestUDUB(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
	 ril_send_command(thiz, "AT+CHV", NULL);
	 RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	 return;
}

static void requestConference(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
	ril_send_command(thiz, "AT^FLASH", NULL);
	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	return;
}

static void requestAnswer(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
	CcContext* ctx = ccGetContext(thiz);

	ril_send_command(thiz, "ATA", NULL);
	if(ctx->has_wait_call)
	{
		ctx->has_wait_call = 0;
	}
	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	return;
}

static void requestSwitchWaitingOrHoldingAndActive(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
	ril_command_exec_for_result(thiz, "AT^FLASH");
	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	return;
}

static void requestHangupWaittingOrBackground(RIL* thiz, void* data, size_t datalen, RIL_Token t)
{
	CcContext* ctx = ccGetContext(thiz);

	if(ctx->has_wait_call == 0)
	{
		ril_command_exec_for_result(thiz, "AT+CHV");
	}
	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	return;
}

static void requestHangupForegroundRresumeBackground(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
	ril_send_command(thiz, "AT+CHV", NULL);

	RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);

	return;
}

static void requestHangup(RIL* thiz, void *data, size_t datalen, RIL_Token t)
{
    int *p_line;
    int ret;
    char cmd[32] = {0};

    p_line = (int *)data;

	snprintf(cmd, 32, "AT+CHV");
    ret = ril_send_command(thiz, cmd, NULL);
    /* success or failure is ignored by the upper layer here.
       it will call GET_CURRENT_CALLS and determine success that way */
    RIL_onRequestComplete(thiz, t, RIL_E_SUCCESS, NULL, 0);
}

/**
 * Called by atchannel when an unsolicited line appears
 * This is called on atchannel's reader thread. AT commands may
 * not be issued here
 */
int RIL_initialize_cc (RIL* thiz)
{
	return 1;
}

static void onUnsolicited_CEND(RIL* thiz, const char* s)
{
	int ignored = 0;
	int cause = 0;

	if(at_line_scanf_int_int_int_int((char*)s, &ignored, &ignored, &ignored, &cause))
	{
		CcContext* ctx = ccGetContext(thiz);
		
		switch(cause)
		{
		case 17:
			ctx->failCase = CALL_FAIL_BUSY;
			break;

		case 42:
			ctx->failCase = CALL_FAIL_CONGESTION;
			break;

		case 68:
			ctx->failCase = CALL_FAIL_ACM_LIMIT_EXCEEDED;
			break;

		case 55:
			ctx->failCase = CALL_FAIL_CALL_BARRED;
			break;

		case 1009:
			ctx->failCase = CALL_FAIL_FDN_BLOCKED;
			break;

		default:
			ctx->failCase = CALL_FAIL_NORMAL;
			break;
		}
	}

	RIL_onUnsolicitedResponse (thiz,
		RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0);

	return;
}

static void onUnsolicited_CCWA(RIL* thiz, const char* s)
{
	RIL_CDMA_CallWaiting_v5 wait_info;
	char* line = NULL;
	CcContext* ctx = ccGetContext(thiz);

	ctx->has_wait_call = 1;
	line = strdup(s);
	memset(&wait_info, 0, sizeof(RIL_CDMA_CallWaiting_v5));
	at_line_scanf_string(line, &wait_info.number);
	wait_info.numberPresentation = 0;
	RIL_onUnsolicitedResponse(thiz, RIL_UNSOL_CDMA_CALL_WAITING,
							&wait_info, sizeof(RIL_CDMA_CallWaiting_v5));
	free(line);
	return;
}

static void onUnsolicited_Ring(RIL* thiz, const char* s)
{
	RIL_onUnsolicitedResponse(thiz,
			RIL_UNSOL_CALL_RING, NULL, 0);
	RIL_onUnsolicitedResponse (thiz,
			RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
			NULL, 0);

	if(!RIL_ExistCalls(thiz)) 
	{
		sleep(3);
	}
	return;
}

int RIL_onUnsolicited_cc (RIL* thiz, const char* s)
{
	int ret = 0;

	if (strStartsWith(s,"RING")
		|| strStartsWith(s, "CRING"))
	{
		onUnsolicited_Ring(thiz, s);
		ret = 1;
	}
	else if(strStartsWith(s,"+CCWA"))
	{
		onUnsolicited_CCWA(thiz, s);
		ret = 1;
	}
	else if (strStartsWith(s,"^ORIG")
		 || strStartsWith(s,"NO CARRIER")
		 || strStartsWith(s, "+CLIP")
		 || strStartsWith(s, "^CONN"))
	{
        RIL_onUnsolicitedResponse (thiz,
            RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
            NULL, 0);
		ret = 1;
	}
	else if(strStartsWith(s, "^CEND")) 
	{
		onUnsolicited_CEND(thiz, s);
	}
	else if(strStartsWith(s, "NO CARRIER")
		|| strStartsWith(s, "NO ANSWER")
		|| strStartsWith(s, "NO DIALTONE"))
	{
		ccHandleCallResult(thiz, s);
	}
	else
	{
		ret = 0;
	}

	return ret;
}

int RIL_onRequest_cc (RIL* thiz, int request, void *data, size_t datalen, RIL_Token t)
{
	switch (request)
	{
	case RIL_REQUEST_GET_CURRENT_CALLS:
		requestGetCurrentCalls(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_DIAL:
		requestDial(thiz, data, datalen, t);
		break;
	
	case RIL_REQUEST_HANGUP:
		requestHangup(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
		requestHangupWaittingOrBackground(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
		requestHangupForegroundRresumeBackground(thiz, data, datalen, t);
        break;

    case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE:
		requestSwitchWaitingOrHoldingAndActive(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_ANSWER:
		requestAnswer(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_CONFERENCE:
		requestConference(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_UDUB:
		requestUDUB(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_DTMF:
		requestDtmf(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_DTMF_START:
		requestDtmfStart(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_DTMF_STOP:
		requestDtmfStop(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_LAST_CALL_FAIL_CAUSE:
		requestLastCallFailCause(thiz, data, datalen, t);
		break;
	
	case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE:
		/*TODO: */
		RIL_onRequestComplete(thiz, t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
		break;

	case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE:
		/*TODO: */
		RIL_onRequestComplete(thiz, t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
		break;

	case RIL_REQUEST_CDMA_FLASH:
		requestCdmaFlash(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_CDMA_BURST_DTMF:
		requestCdmaBurstDtmf(thiz, data, datalen, t);
		break;

	case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE:
		/*TODO: */
		RIL_onRequestComplete(thiz, t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
		break;

	case RIL_REQUEST_SEPARATE_CONNECTION:
	default:
		RIL_onRequestComplete(thiz, t, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0);
		break;
	}

	return 1;
}

